﻿using System;

namespace RLProject1.Model.AMEEWorld
{
    /// <summary>
    /// Konfigurationklasse mit Informationen zur disjunkten Partitionierung der Welt (des Zustands).
    /// </summary>
    /// <remarks>Eine Einteilung mit Überlappenden 'Kacheln' wird unter verwendung mehrere Tilings realisiert.</remarks>
    public class Tiling
    {
        /// <summary>
        /// Radien der Spuren (vom Zentrum der egozentrischen Karte 
        /// <see cref="RLProject1.Model.AMEEWorld.WorldImage">WorldImage</see>)<para>
        /// Readonly Referenz auf das float-Array. Änderung der Werte kann zu Daten-Inkonsistenz führen!</para>
        /// </summary>
        internal readonly float[] SpurRadien;

        /// <summary>
        /// Gibt Anzahl von Unebenheitspartitionen.
        /// </summary>
        /// <value>
        /// The unebenheitspartitionen.
        /// </value>
        /// <remarks>Seiteneffekt: Set führt zur Neuberechnung von Dimension.</remarks>
        public int Unebenheitspartitionen { 
            get { return _unebenheiten; }
            private set { _unebenheiten = value; calcDimension(); } 
        }
        int _unebenheiten;

        /// <summary>
        /// Gibt Gesamtzahl Sektoren.
        /// </summary>
        /// <value>
        /// The sektoren.
        /// </value>
        /// <remarks>Seiteneffekt: Set führt zur Neuberechnung von Dimension.</remarks>
        public int Sektoren { 
            get { return _sektoren; } 
            private set { _sektoren = value; calcDimension(); } 
        }
        int _sektoren;

        /// <summary>
        /// Gibt Gesamtzahl Spuren
        /// </summary>
        /// <value>
        /// The spuren.
        /// </value>
        /// <remarks>Seiteneffekt: Set fhrt zur Neuberechnung von Dimension.</remarks>
        public int Spuren {
            get { return _spuren; }
            private set { _spuren = value; calcDimension(); }
        }
        int _spuren;

        /// <summary>
        /// Gibt die Gesamtzahl Abschnitte. Äquivalent zu Sektoren * Spuren.
        /// </summary>
        public int Abschnitte {
            get { return this.Sektoren * this.Spuren; }
        }

        /// <summary>
        /// Gibt die Gesamtzahl an Zielrichtungen. Äquivalent zu Sektoren.
        /// </summary>
        /// <value>
        /// The zielrichtungen.
        /// </value>
        public int Zielrichtungen {
            get { return this.Sektoren; }
        }

        /// <summary>
        /// Gibt die Anzahl möglicher Aktionen.<para>
        /// Im einfachen Standardfall (wie hier) ist diese äquivalent zu Tiling.Abschnitte</para>
        /// </summary>
        public int Aktionen { get; private set; }


        /// <summary>
        /// Gibt die Tilingdimension (Anzahl Weltpartitionen):<para>
        /// (#Ziel-Sektoren * #U-Partitionen^(#Abschnitte)) = Dimension
        /// </para>
        /// </summary>
        /// <remarks>Seiteneffekt: Set führt zur Neuberechnung von Dimension.<para>
        /// Note: Dimension * Aktionen entspricht der Größe des Theta- und Phi-Vektors bei Sarsa-lambda mit Linear-GradientAbstiegsmethode (siehe Sutton 8.3)</para></remarks>
        public int Dimension {
            get { return _dimension; }
        }
        int _dimension;
        void calcDimension() { 
            _dimension = this.Zielrichtungen * (int)Math.Pow(this.Unebenheitspartitionen, this.Abschnitte); 
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Tiling"/> class.
        /// </summary>
        /// <param name="anzUnebenheiten">Anzahl der Intervalle zwischen 0.0 und 1.0.<para>
        ///     Intern werden noch zwei Intervalle für kleiner 0 und größer 1 hinzugefügt.</para></param>
        /// <param name="anzSektoren">The Anzahl Sektoren (Kreisabschnitte)</param>
        /// <param name="spurRadien">Die Spurradien. Länge des Arrays wird zu Anzahl 'Spuren'.
        /// null: 'Spuren'=null & 'SpurRadien'=leeres Array</param>
        /// <exception cref="RLProjectException">anzUnebenheiten oder anzSektoren kleiner 1.</exception>
        public Tiling(int anzUnebenheiten, int anzSektoren, float[] spurRadien) {
            if (anzSektoren <= 0)
                throw new RLProjectException("Tiling Instanziierungsfehler",new ArgumentException("Anzahl Sektoren muss mindestens 1 sein."));
            if (anzUnebenheiten <= 0)
                throw new RLProjectException("Tiling Instanziierungsfehler", new ArgumentException("Anzahl Intervalle / Partitionen muss mindestens 1 sein."));
            this._unebenheiten = anzUnebenheiten + 2;
            this._sektoren = anzSektoren;
            this._spuren = spurRadien != null ? spurRadien.Length : 0;
            // standard Aktionen = 'Gehe zu Abschnitt xy'
            this.Aktionen = this.Abschnitte;
            this.calcDimension();
            this.SpurRadien = spurRadien != null ? spurRadien : new float[0];
        }

        

        ///// <summary>
        ///// Berechnet the unebenheiten.
        ///// U-Maß get von 0.0 - 1.0. Zusätliche Partitionen für Sonderfälle kleiner 0 und größer 1.
        ///// Gleichmäßige Partitionierung in 'Unebenheitspartitionen'-2 Teile des Intervalls [0,1] plus je
        ///// eine Partion für größer 1 bzw kleiner 0.
        ///// </summary>
        //void berechneUnebenheiten() {
        //    // NegInf | 0 | 0.33 | 0.66 | 1.0 | PosInf 
        //}

        /// <summary>
        /// Gibt die Nummer der Partition, in dem der gegebene Unebenheitswert liegt.
        /// </summary>
        /// <param name="unebenheitswert">Der Unebenheitswert.</param>
        /// <returns>ID bzw. Index der Partition. Wert 0 bis Unebenheitspartitionen-1.</returns>
        /// <remarks>U-Maß get von 0.0 - 1.0. Zusätliche Partitionen für Sonderfälle kleiner 0 und größer 1 (inkl.).
        /// Gleichmäßige Partitionierung in 'Unebenheitspartitionen'-2 gleichgroße Teile des Intervalls ]0,1[ plus je
        /// eine Partion für größer 1 bzw kleiner 0 (je inkl.).</remarks>
        public virtual int GibUPartitionsindex(double unebenheitswert) {
            // Sonderfälle:
            if (unebenheitswert <= 0)
                return 0;
            if (unebenheitswert >= 1.0)
                return this.Unebenheitspartitionen - 1;
            //floor(u/(1/AnzUPart))+1
            int retval = (int)(unebenheitswert * (double)(this.Unebenheitspartitionen - 2)) + 1;
            return retval;
        }

        /// <summary>
        /// Gibt the U mittelwert.
        /// </summary>
        /// <param name="uPartitionsIndex">Index of the u partitions.</param>
        /// <returns></returns>
        /// <exception cref="RLProjectException">
        /// Partitionsindex &lt; 0.
        /// or
        /// Partitionsindex über Bereich (Unebenheitspartitionen-1)
        /// </exception>
        public virtual double GibUMittelwert(int uPartitionsIndex) {
            if (uPartitionsIndex < 0)
                throw new RLProjectException("Partitionsindex < 0.", new IndexOutOfRangeException());
            if (uPartitionsIndex >= this.Unebenheitspartitionen)
                throw new RLProjectException("Partitionsindex über Bereich (Unebenheitspartitionen-1)", new IndexOutOfRangeException());
            //Sonderfälle
            if (uPartitionsIndex == 0)
                return Double.NegativeInfinity;
            if (uPartitionsIndex == this.Unebenheitspartitionen - 1)
                return Double.PositiveInfinity;
            //'Normal'Fall
            double intervallLength = 1.0 / (this.Unebenheitspartitionen - 2);
            double retVal = intervallLength * ((double)uPartitionsIndex - 0.5);// retVal = uPartitionsIndex * intervallLength - intervallLength / 2;
            return retVal;
        }

        /// <summary>
        /// Gibt die Abschnittsnummer zu gebener Spur- / Sektor-ID
        /// </summary>
        /// <param name="spur">The spur.</param>
        /// <param name="sektor">The sektor.</param>
        /// <returns>(spur * Tiling.Sektoren) + sektor;</returns>
        public virtual int GibAbschnittsnummer(int spur, int sektor) {
            // TODO testen, und wird das gebraucht? (bisher nicht)
            return spur * this.Sektoren + sektor;
        }

        #region Overrides
        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode() {
            int hash = this.Sektoren;
            hash = hash * 7 + this.Spuren;
            hash = hash * 13 + this.Unebenheitspartitionen;
            hash = hash * 23 + this.Aktionen;
            if (SpurRadien != null) {
                for (int i = 0; i < SpurRadien.Length; i++) {
                    hash += hash * 31 + SpurRadien[i].GetHashCode();
                }
            }
            return hash;
        }

        /// <summary>
        /// Determines whether the specified <see cref="System.Object" /> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object obj) {
            Tiling other = obj as Tiling;
            if (other == null)
                return false;
            if (other.Sektoren != this.Sektoren)
                return false;
            if (other.Spuren != this.Spuren)
                return false;
            if (other.Unebenheitspartitionen != this.Unebenheitspartitionen)
                return false;
            if (other.SpurRadien != null) {
                if (this.SpurRadien != null) {
                    for (int i = 0; i < SpurRadien.Length; i++) {
                        if (other.SpurRadien[i] != this.SpurRadien[i])
                            return false;
                    }
                }
                else
                    return false;
            }
            else if (this.SpurRadien != null) {
                    return false;
            }
            return true;
        }

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>AMEEWorld.Tiling(Spuren={0}, Sektoren={1}, Unebenheiten={2}, Aktionen={3})</returns>
        public override string ToString() {
            return String.Format("AMEEWorld.Tiling(Spuren={0}, Sektoren={1}, Unebenheiten={2}, Aktionen={3})",
                this.Spuren,
                this.Sektoren,
                this.Unebenheitspartitionen,
                this.Aktionen);
        }
        #endregion

    }
}
